外部ネットワークアクセスで AWS サービスに対する IAM 認証を試してみた #SnowflakeDB
はじめに
2024年7月末のアップデートで外部ネットワークアクセスを使用する Snowpark UDF やストアドプロシージャから AWS サービスに対する IAM 認証のサポートがパブリックプレビューとなりました。こちらを試してみましたので、本記事で内容をまとめてみます。
アップデートの概要
Snowflake では外部ネットワークアクセスとして UDF やストアドプロシージャから外部の API を呼び出す処理を作成できます。この機能はすでに一般提供となっており、例えば以下の記事では Snowflake から dbt Cloud の API を呼び出し、タスクを使用しジョブをトリガーしています。
外部ネットワークから AWS リソースにアクセスする場合、これまでは IAM アクセスキーを使用する必要がありましたが、今回のアップデートにより、Snowflake から各種 AWS サービスへ直接 IAMロールで認証できるようになりました。これにより、一時的な認証情報を使用し、アクセスキーを使わずに安全にリソースへアクセスできるため、セキュリティリスクが低減されます。
前提条件
ここでは以下の環境で検証を行いました。
- Python UDF を使用
- UDF から外部ネットワークアクセスで Amazon API Gateway エンドポイントを使用した Lambda 関数を呼び出す
- この際、IAM認証を使用する
事前準備
Lambda 関数の作成
下図の設定で関数を作成しました。
実際に行われる処理は以下のように event オブジェクトから name の値を取得し、その値に応じて異なる挨拶メッセージを返してくれるようにしました。
import json
def lambda_handler(event, context):
# eventからnameの値を取得
name = event.get('name')
# nameがある場合は "Hello, name" の形式で、ない場合は "Hello!" を返す
greeting = f"Hello, {name}" if name else "Hello!"
return {
'statusCode': 200,
'body': json.dumps(greeting)
}
処理ができたら保存してテストを実行しておきました。
API Gateway の作成
続けて API Gateway を作成します。「API を作成」から「REST API」 を作成します。
リソースを以下のパスで作成しました。
メソッドのタイプを「POST」として統合タイプに「Lambda 関数」を指定します。
IAM 認証を使用したいので、REST API の IAM 認証を有効にします。「メソッドリクエスト」を編集します。
[認可] に「AWS IAM」を指定し保存します。
作成した API をここではdev
ステージを作成しデプロイしました。
IAM ロールの作成
API Gateway へのアクセスに使用する IAM ロールを以下の設定で作成します。
- 信頼されたエンティティのタイプ:AWS アカウント
- オプション
- 外部 ID を要求する
アカウントIDには自身のアカウントを指定し、外部 ID にも適当な値を指定しておきます。これらの値は後述する手順で更新します。今回はリソースポリシーでアクセス制御を行うため、IAM ポリシーは特に関連付けませんでした。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"AWS": "<自身のAWSアカウントID>"
},
"Condition": {
"StringEquals": {
"sts:ExternalId": "<任意の値>"
}
}
}
]
}
API Gateway のリソースポリシーを定義
次に先ほど作成した API Gateway のリソースポリシーを編集します。IAM ロールの Arn を使用し、以下の内容でリソースポリシーを作成しました。Resource
は API Gateway のメソッド ARN を指定します。
{
"Version": "2012-10-17",
"Statement":
[
{
"Effect": "Allow",
"Principal":
{
"AWS": "arn:aws:sts::xxxxxxxxxxxx:assumed-role/<IAMロール名>/snowflake"
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:ap-northeast-1:xxxxxxxxxxxx:xxxxxx/*/POST/hello"
}
]
}
上記は以下を参考としました。
ポリシーを保存後、再度デプロイしておきます。
Snowflake側の作業
ここから Snowflake 側でも作業を行います。IAM認証を使用する外部ネットワークアクセスでは、以下の作業を行います。
- ネットワークルールの作成
- AWS IAM 認証用のセキュリティ統合の作成
- シークレットの作成
- 外部アクセス統合の作成
ネットワークルールの作成
はじめにMODE = EGRESS
とするネットワークルールを作成します。これにより外部ネットワークへのアクセスの許可・制限を行います。ネットワークルールはスキーマレベルのオブジェクトなので、何らかのスキーマを指定し作成します。
VALUE_LIST
には、対象の API のホストを指定しました。
CREATE OR REPLACE NETWORK RULE aws_api_gateway_network_rule
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = ('{api_id}.execute-api.ap-northeast-1.amazonaws.com');
セキュリティ統合の作成
次に、IAM を使用する外部認証用のセキュリティ統合を作成します。これにより、Snowflake アカウント側でセキュリティ統合が参照する IAM ユーザーが作成されます。AWS_ROLE_ARN
には上記の手順で作成した IAM ロールの Arn を指定します。デフォルトでは ACCOUNTADMIN のみ実行可能です。
CREATE OR REPLACE SECURITY INTEGRATION aws_api_gateway_security_integration
TYPE = API_AUTHENTICATION
AUTH_TYPE = AWS_IAM
ENABLED = TRUE
AWS_ROLE_ARN = 'arn:aws:iam::xxxxxxxxxxxx:role/snowflake-api-gateway-role';
統合オブジェクトを作成後、以下を実行します。
DESC SECURITY INTEGRATION aws_api_gateway_security_integration;
出力の内、以下の内容を後ほど使用するので控えておきます。
- API_AWS_IAM_USER_ARN
- 統合オブジェクトが参照する Snowflake アカウント側の IAM ユーザー
- API_AWS_EXTERNAL_ID
AWS側:IAM ロールの信頼関係を更新
Snowflake と AWS 側で作成済みの IAM ロール間の信頼関係の設定を行います。先の手順で取得した API_AWS_IAM_USER_ARN と API_AWS_EXTERNAL_ID の値でロールの信頼関係を以下の通り更新し保存します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {
"AWS": "<API_AWS_IAM_USER_ARN>"
},
"Condition": {
"StringEquals": {
"sts:ExternalId": "<API_AWS_EXTERNAL_ID>"
}
}
}
]
}
シークレットの作成
外部サービスに必要な資格情報を保持するためにTYPE = CLOUD_PROVIDER_TOKEN
とするシークレットを作成します。API_AUTHENTICATION
には上記の手順で作成したセキュリティ統合オブジェクトを指定します。シークレットもスキーマレベルのオブジェクトのため、何らかのスキーマを指定し作成します。
CREATE OR REPLACE SECRET aws_api_gateway_access_token
TYPE = CLOUD_PROVIDER_TOKEN
API_AUTHENTICATION = aws_api_gateway_security_integration;
外部アクセス統合の作成
さいごに外部アクセス統合を作成し、これまでに作成した各種オブジェクトを紐づけます。注意点として、現時点ではトライアルアカウントはサポートされていません。デフォルトでは ACCOUNTADMIN のみ実行可能です。
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION aws_api_gateway_external_access_integration
ALLOWED_NETWORK_RULES = (aws_api_gateway_network_rule)
ALLOWED_AUTHENTICATION_SECRETS = (aws_api_gateway_access_token)
ENABLED = TRUE
COMMENT = 'Test API Gateway connectivity';
PythonUDF の作成
IAM 認証で作成した API を呼び出す Python UDF を定義します。ここでは以下の内容で UDF を作成しました。
CREATE OR REPLACE FUNCTION aws_api_gateway_post_function(name STRING)
RETURNS VARCHAR
LANGUAGE PYTHON
EXTERNAL_ACCESS_INTEGRATIONS = (aws_api_gateway_external_access_integration)
RUNTIME_VERSION = '3.11'
SECRETS = ('cred' = <db>.<schema>.aws_api_gateway_access_token)
PACKAGES = ('requests', 'aws-requests-auth')
HANDLER = 'main_handler'
AS
$$
from aws_requests_auth.aws_auth import AWSRequestsAuth
import requests
import _snowflake
import json
def main_handler(name):
# シークレットから一時的な認証情報を取得
cloud_provider_object = _snowflake.get_cloud_provider_token('cred')
# AWSRequestsAuthに直接資格情報を渡す
auth = AWSRequestsAuth(
aws_access_key=cloud_provider_object.access_key_id,
aws_secret_access_key=cloud_provider_object.secret_access_key,
aws_token=cloud_provider_object.token,
aws_host='{api_id}.execute-api.ap-northeast-1.amazonaws.com',
aws_region='ap-northeast-1',
aws_service='execute-api'
)
# nameパラメータでペイロードを定義
payload = {'name': name}
# API GatewayのエンドポイントにPOSTリクエストを送信
url = 'https://{api_id}.execute-api.ap-northeast-1.amazonaws.com/dev/hello'
response = requests.post(url, json=payload, auth=auth)
# JSONレスポンスをパースしてbodyを抽出
response_data = json.loads(response.text)
body_content = json.loads(response_data["body"])
return body_content
$$;
ポイントは以下です。
- Snowflake 内に保存されているシークレットデータ(認証情報やAPIトークンなど)の取得には
_snowflake
モジュールを使用get_cloud_provider_token(cloud_provider_secret_name)
でセッションを作成するための情報を持つオブジェクトが返される- 属性として、
access_key_id
、secret_access_key
、token
が含まれる
- API Gateway に認証付きリクエストを送信するために、AWSRequestsAuth を使用
- こちらは以下を参考とさせていただきました
UDF を実行
定義した UDF を実行してみます。
実行結果は以下の通りとなり、API Gateway 経由で Lambda 関数を実行し結果を取得することができました。
>SELECT aws_api_gateway_post_function('Alice');
+----------------------------------------+
| AWS_API_GATEWAY_POST_FUNCTION('ALICE') |
|----------------------------------------|
| Hello, Alice |
+----------------------------------------+
1 Row(s) produced. Time Elapsed: 3.982s
>SELECT aws_api_gateway_post_function('John');
+---------------------------------------+
| AWS_API_GATEWAY_POST_FUNCTION('JOHN') |
|---------------------------------------|
| Hello, John |
+---------------------------------------+
1 Row(s) produced. Time Elapsed: 2.101s
さいごに
Snowpark UDF から AWS サービスに対する IAM 認証を使用する外部ネットワークアクセスを定義してみました。一時的な認証情報を使用するので、AWS のプラクティスにも沿って Snowflake からアクセスできます。
本記事では API Gateway を使用してみましたが、公式ドキュメントでは S3 に対するアクセスを IAM 認証で行う手順がサンプルとして公開されています。
他にも以下では Amazon Bedrock に対して IAM 認証を行う例が記載されています。
各サービスで利用できるの積極的に使っていきたい機能と思います。
こちらの内容が何かの参考になれば幸いです。